Skip to content

S08-01 反射-反射 ​

[TOC]

概述 ​

入门需求 ​

入门需求:

根据配置文件re.properties指定的类路径和方法名,创建对象并调用方法,无需修改源码(符合开闭原则)。

properties
# re.properties

classfullpath=com.hspedu.Cat
method=hi

实现:传统方案

无法通过字符串动态创建对象和调用方法,必须修改源码。


实现:反射方案

java
package com.hspedu.reflection.question;

import java.io.FileInputStream;
import java.util.Properties;
import java.lang.reflect.Method;
import java.lang.reflect.Class;

/**
 * 实现:反射方案
 */
@SuppressWarnings({"all"})
public class ReflectionQuestion {
  public static void main(String[] args) throws Exception {
    // 1. 读取配置文件
    Properties properties = new Properties();
    properties.load(new FileInputStream("src\\re.properties"));
    String classfullpath = properties.get("classfullpath").toString();
    String methodName = properties.get("method").toString();

    // 2. 反射机制创建对象并调用方法
    Class cls = Class.forName(classfullpath); // 加载类
    Object o = cls.newInstance(); // 创建对象
    Method method = cls.getMethod(methodName); // 获取方法对象(在反射中,可以将方法也视为对象)
    method.invoke(o); // 调用方法(方法.invoke(对象))
  }
}

反射机制 ​

Java 的反射机制(Reflection) 是 Java 语言被视为“准动态语言”的核心特性。它允许运行中的 Java 程序直接检查类的内部结构,并在运行时读取、修改或调用任意类、属性与方法。

机制概述 ​

正常情况下,编译期就已经确定了要使用的类与方法(静态加载)。而反射机制允许程序在运行期间根据字符串名称动态加载类、创建实例以及执行方法(动态加载)。

其核心原理依赖于 JVM 在类加载阶段于堆内存中创建的 java.lang.Class 对象。每一个类被加载后,JVM 都会为其生成一个唯一的 Class 实例,包含了该类的完整结构信息。

核心 API ​

反射功能主要由 java.lang.Class 类以及 java.lang.reflect 包中的组件提供:

核心类 / 接口说明
Class<T>表示正在运行的 Java 应用程序中的类或接口,是反射的入口
Constructor<T>提供有关类的单一构造方法的信息和访问权限
Method提供类或接口上单一方法的信息和访问权限
Field提供类或接口上单一属性(字段)的信息和动态访问
Modifier用于解码类和成员变量的修饰符(如 public、static、final 等)

应用场景 ​

应用场景:反射是现代 Java 框架与底层的基石,主要应用在以下领域:

  • 主流框架依赖:Spring IOC 容器解析 XML 或注解配置,利用反射动态实例化 Bean 并完成依赖注入;MyBatis 利用反射将数据库结果集映射为实体类。
  • 动态代理技术:JDK 动态代理基于反射在运行时动态生成代理类,是 AOP(面向切面编程)与事务管理的核心实现方式。
  • 注解运行时解析:通过在运行期利用反射读取类、字段或方法上的自定义注解(RetentionPolicy.RUNTIME),实现逻辑增强。
  • 开发工具与 IDE:开发环境的自动补全提示、单元测试框架(如 JUnit 识别 @Test 注解并运行测试)均严重依赖反射。

优缺点 ​

优势:

  • 极高的灵活性:解耦程序,无需在编译时硬编码具体类,增强模块拓展性。
  • 通用性强:适合编写插件、框架、中间件等与业务逻辑脱钩的底层系统。

劣势:

  • 性能开销较大:反射涉及类解析、方法查找与安全检查等动态步骤,性能明显低于直接执行字节码。
  • 破坏面向对象封装性:通过 setAccessible(true) 可以绕过 private 访问控制,可能导致非预期的内部状态篡改。
  • 安全限制与编译期检查缺失:反射代码发生在运行时,无法在编译期捕获拼写错误或类型不匹配,容易引发 ClassNotFoundException 等运行时异常。

反射性能 ​

Java 中的反射机制虽然带来了强大的动态性,但其性能相比原生代码调用存在明显劣势。在大规模、高频调用的场景(如 ORM 框架、RPC 框架、RPC 序列化)中,反射性能优化是必不可少的环节。

性能测试 ​

java
package p01_reflection;

import p01_Cat;
import java.lang.reflect.Method;

public class ReflectionPerformanceTest {
    public static void main(String[] args) throws Exception {
        m1(); // 传统方式
        m2(); // 反射方式
        m3(); // 反射优化方式
    }

    // 1. 传统方式
    public static void m1() {
        Cat cat = new Cat();
        long start = System.currentTimeMillis();
        for (int i = 0; i < 900000000; i++) {
            cat.hi();
        }
        long end = System.currentTimeMillis();
        System.out.println("m1() 耗时=" + (end - start)); // 10ms
    }

    // 2. 反射方式
    public static void m2() throws Exception {
        Class cls = Class.forName("com.hspedu.Cat");
        Object o = cls.newInstance();
        Method hi = cls.getMethod("hi");
        long start = System.currentTimeMillis();
        for (int i = 0; i < 900000000; i++) {
            hi.invoke(o);
        }
        long end = System.currentTimeMillis();
        System.out.println("m2() 耗时=" + (end - start)); // 1545ms
    }

    // 3. 反射优化(关闭访问检查

性能损耗根源 ​

性能损耗根源:在进行性能优化前,需要明确反射调用慢的核心原因:

  • 安全与权限校验:每次通过反射调用方法或访问属性时,JVM 默认都会检查调用者是否具备访问权限(checkAccess)。
  • 参数装箱与临时数组:反射方法的入参为 Object[]。如果传递基本数据类型,会触发隐式的自动装箱(Boxing),且每次调用都需要在堆上创建参数数组。
  • JIT 编译器无法内联:普通方法调用在 JVM 中可被 JIT(即时编译器)识别并进行方法内联(Inlining)和逃逸分析;而反射调用属于动态派发,JIT 难以进行深入优化。
  • 字符串查找开销:通过 Class.getMethod("name") 查找方法时,需要遍历类的成员列表并进行字符串匹配,开销较大。

优化:关闭安全检查 ​

通过调用 Field、Method、Constructor 的 setAccessible(true),可以绕过 JVM 每次调用时的权限安全检查。即使是 public 方法,显式设置为 true 也能带来 2 到 4 倍的性能提升。

java
Field field = clazz.getDeclaredField("age");
field.setAccessible(true); // 抑制 Java 语言访问检查,大幅降低开销

优化:缓存元数据对象 ​

切勿在频繁调用的代码块中执行 Class.forName()、getMethod() 或 getDeclaredField()。应当将解析出的 Method、Field 对象缓存在内存中(如静态常量或 ConcurrentHashMap 中)。

java
// ❌ 错误示例:每次调用都重新查找
public Object invokeUncached(Object target, String methodName) throws Exception {
  Method method = target.getClass().getMethod(methodName); // 高昂的检索开销
  return method.invoke(target);
}

// ✅ 优化示例:使用缓存
private static final Map<String, Method> METHOD_CACHE = new ConcurrentHashMap<>();

public Object invokeCached(Object target, String methodName) throws Exception {
  Method method = METHOD_CACHE.computeIfAbsent(
    target.getClass().getName() + "#" + methodName,
    key -> {
      try {
        return target.getClass().getMethod(methodName);
      } catch (NoSuchMethodException e) {
        throw new RuntimeException(e);
      }
    }
  );
  return method.invoke(target);
}

Class~ ​

核心机制 ​

在 Java 中,java.lang.Class 不仅仅是一个普通的类,它是 JVM 实现面向对象特性、动态方法派发、反射以及运行时类型识别(RTTI)的核心纽带。

本质定义 ​

在 JVM 内部,Class 对象是元数据的堆内镜像。

Java 类的编译产物(.class 字节码)被加载入内存后,JVM 内部会维护两份关键数据:

  1. 底层元数据(C++ 层):JVM 在元空间(Metaspace)中创建一个 InstanceKlass 结构体,记录该类的字节码信息、常量池、方法表及虚表等。

  2. 镜像对象(Java 层):JVM 在堆内存(Heap)中自动创建一个 java.lang.Class 实例,作为 Java 开发者在代码中访问元空间数据的物理入口。

image-20260804174249706

唯一性判定 ​

在 JVM 中,决定一个 Class 对象的唯一身份,不仅取决于类的全限定名,还取决于加载它的类加载器。

JVM 内部判定类型唯一性的逻辑为:

Class 唯一性=类全限定名(Binary Name)+定义类加载器(Defining ClassLoader)\text{Class 唯一性} = \text{类全限定名(Binary Name)} + \text{定义类加载器(Defining ClassLoader)}

  • 隔离效应:同一个 .class 字节码文件,如果被不同的 ClassLoader 实例加载,JVM 会在堆中生成两个完全独立的 Class 对象。
  • 类型安全:两个由不同 ClassLoader 加载的同名类,其实例之间无法进行强制类型转换或直接赋值,否则会抛出 ClassCastException。

生命周期 ​

Class 对象的产生与 JVM 的类加载过程密切相关。类从被加载到退出内存,经历加载、验证、准备、解析、初始化、使用、卸载七个阶段。

产生时刻 ​

Class 对象并不是在类“初始化”阶段才创建的,而是在加载(Loading)阶段由 JVM 在堆区直接生成。

类初始化的触发条件 ​

虽然 Class 对象在加载期就已经存在,但类中的静态代码块(static {})和静态变量赋值只会在初始化阶段触发。

  • 主动引用(触发初始化):

  • 使用 new 关键字实例化对象。

  • 读取或设置类的非 final 静态字段。

  • 调用类的静态方法。

  • 使用 Class.forName("...") 且未将 initialize 参数设为 false。

  • 初始化类的子类(会优先触发父类初始化)。

  • 被动引用(不触发初始化):

  • 通过类名获取 Class 字面量(如 User.class)。

  • 通过子类引用父类的静态字段(只会初始化父类)。

  • 引用编译期常量(static final 类型的基本数据类型或字符串)。

  • 定义该类型的数组(如 User[] users = new User[10])。

多态与虚表 ​

Class 所持有的元数据是 JVM 实现多态(Polymorphism)与动态方法重写的物理基础。

每个 Class 对应的元数据中,都维护了虚方法表(vtable)与接口方法表(itable):

  1. 方法表建立:JVM 在类加载的链接(Linking)阶段,为每个类生成 vtable,按固定索引存放所有可能被调用的虚方法(非 private、非 static、非 final 的实例方法)的实际入口指针。

  2. 方法重写定位:如果子类重写了父类方法,子类 vtable 中对应索引位置的方法指针会更新指向子类自己的代码;若未重写,则依然指向父类实现。

  3. 指令执行:当 JVM 执行 invokevirtual 指令时,首先通过对象头中的类型指针找到具体的 Class 元数据,再通过 vtable 中的固定偏移量迅速定位并跳转到真实的方法地址。

泛型与擦除 ​

Java 泛型采用的是伪泛型(Type Erasure)机制,但在 Class 层面保留了类型补偿手段:

  • 运行期擦除:在编译后,List<String> 和 List<Integer> 在运行期对应的 Class 对象都是同一个 java.util.List.class。
  • 签名保留(Signature Attribute):方法体内部的泛型参数会被擦除为 Object 或其上界,但类声明、字段声明以及方法签名上的泛型信息会被写入字节码的 Signature 属性集中。
  • 类型提取:框架(如 Spring、Gson)可以通过 Class.getGenericSuperclass() 配合 ParameterizedType 接口,在运行期还原编译期保留的泛型实参。

卸载与回收 ​

Class 对象位于堆内存中,同样接受垃圾回收(GC)管理,但其被卸载(Unload)的条件极其苛刻,必须同时满足以下三条:

  1. 实例全无:该类及其所有子类的所有实例对象都已经处于可回收状态。

  2. 加载器已被回收:加载该类的 ClassLoader 实例已经被 GC 回收。

  3. 反射引用断开:该类对应的 java.lang.Class 对象没有在任何地方被引用(例如无法在任何地方通过反射访问该类)。

JDK 自带的系统类加载器(如 Bootstrap ClassLoader、AppClassLoader)本身生命周期贯穿 JVM 运行始终,因此它们加载的类在 JVM 运行期间永远不会被卸载;只有自定义类加载器加载的类在加载器被回收后才有可能被卸载。

API: Class ​

构造方法与对象实例化 ​

  • Class<T> Class():(ClassLoader loader, Class<?> recordComponentArrayClass),构造方法。JVM 内部专用私有构造方法,外部无法直接显式调用。

  • T newInstance():(),实例化无参对象。通过默认无参构造方法创建当前类的实例(自 JDK 9 起废弃,推荐使用 getDeclaredConstructor().newInstance())。

注意事项:

  1. Class.newInstance() 要求类必须显式提供无参构造方法。若无参构造方法抛出异常或权限受限,该方法抛出的 Checked Exception 未经过包装,易引发运行时隐患。自 JDK 9 起已被标注 @Deprecated。
  2. 现代 Java 应当统一使用 clazz.getDeclaredConstructor().newInstance() 来创建实例,它会将构造方法抛出的异常统一包装为 InvocationTargetException。
java
import java.lang.reflect.Constructor;

public class InstanceCreationDemo {
  public static void main(String[] args) throws Exception {
    Class<StringBuilder> clazz = StringBuilder.class;

    // 推荐做法:通过 Constructor 对象进行实例化
    Constructor<StringBuilder> constructor = clazz.getDeclaredConstructor();
    StringBuilder sb = constructor.newInstance();
    sb.append("Hello Class API");

    System.out.println("动态创建的对象内容: " + sb.toString());
  }
}

获取类与接口元数据 ​

  • String getName():(),获取全限定类名。返回带有包名的全限定名称(如 java.lang.String)。

  • String getSimpleName():(),获取简单类名。返回不带包名的类名(如 String)。

  • Package getPackage():(),获取包元数据。返回当前类所在的 Package 对象。

  • Class<? super T> getSuperclass():(),获取直接父类。返回当前类的直接父类 Class 对象;若当前类为 Object 或接口,则返回 null。

  • Class<?>[] getInterfaces():(),获取实现的接口。返回当前类显式实现的接口 Class 数组。

  • boolean isInterface():(),判断是否为接口。检查当前类型是否表示接口。

  • boolean isArray():(),判断是否为数组。检查当前类型是否为数组类型。

  • Class<?> Class.forName():(String className),加载并获取 Class 对象。根据传入的全限定类名加载类并返回其 Class 实例。

注意事项:

  1. getInterfaces() 仅返回当前类直接实现的接口,不会递归包含父类实现的接口或接口继承的子接口。
  2. 基本数据类型(如 int.class)与包装类(如 Integer.class)的 Class 对象并不相等。基本类型的 Class 对象可通过 Integer.TYPE 或 int.class 获取。
java
import java.io.Serializable;

public class MetadataDemo implements Serializable {
  public static void main(String[] args) {
    Class<MetadataDemo> clazz = MetadataDemo.class;

    System.out.println("全类名: " + clazz.getName());
    System.out.println("简单类名: " + clazz.getSimpleName());
    System.out.println("父类全名: " + clazz.getSuperclass().getName());
    System.out.println("是否为接口: " + clazz.isInterface());

    Class<?>[] interfaces = clazz.getInterfaces();
    for (Class<?> inter : interfaces) {
      System.out.println("实现的接口: " + inter.getName());
    }
  }
}

反射获取成员变量 ​

  • Field[] getFields():(),获取所有公共字段。返回当前类及其所有超类中声明的所有 public 字段。

  • Field[] getDeclaredFields():(),获取本类声明的所有字段。返回当前类显式声明的所有字段(含 private、protected、默认及 public),不包含超类字段。

  • Field getField():(String name),获取指定公共字段。根据字段名查找当前类或超类中的 public 字段。

  • Field getDeclaredField():(String name),获取本类指定声明字段。根据字段名查找本类显式声明的特定字段。

注意事项:

  1. 访问或修改 private 字段前,必须调用 field.setAccessible(true) 打破访问控制校验,否则抛出 IllegalAccessException。
  2. 在 JDK 9+ 模块化系统(JPMS)限制下,若目标类所属模块未对当前模块暴露/开放(opens),即便使用 setAccessible(true) 也会抛出 InaccessibleObjectException。
java
import java.lang.reflect.Field;

class User {
  public String role = "Admin";
  private String secretKey = "K987654321";
}

public class FieldReflectionDemo {
  public static void main(String[] args) throws Exception {
    User user = new User();
    Class<?> clazz = user.getClass();

    // 获取本类所有声明的字段(包含 private)
    Field[] fields = clazz.getDeclaredFields();
    for (Field f : fields) {
      System.out.println("字段名: " + f.getName() + " | 类型: " + f.getType().getSimpleName());
    }

    // 读取私有字段
    Field secretKeyField = clazz.getDeclaredField("secretKey");
    secretKeyField.setAccessible(true); // 抑制 Java 语言权限检查
    String value = (String) secretKeyField.get(user);
    System.out.println("读取私有字段值: " + value);
  }
}

反射获取成员方法 ​

  • Method[] getMethods():(),获取所有公共方法。返回当前类及其超类(含 Object)中声明的所有 public 方法。

  • Method[] getDeclaredMethods():(),获取本类声明的所有方法。返回当前类显式声明的所有方法,不包含继承的方法。

  • Method getMethod():(String name, Class<?>... parameterTypes),获取指定公共方法。根据方法名和形参类型列表查找 public 方法。

  • Method getDeclaredMethod():(String name, Class<?>... parameterTypes),获取本类指定方法。根据方法名和形参类型列表查找本类显式声明的方法。

注意事项:

  1. 获取重载方法时,精准匹配参数类型至关重要。基本数据类型(如 int.class)与包装类型(如 Integer.class)不通用,混淆传参会导致抛出 NoSuchMethodException。
java
import java.lang.reflect.Method;

public class MethodReflectionDemo {
  public static void main(String[] args) throws Exception {
    Calculator calc = new Calculator();
    Class<?> clazz = calc.getClass();

    // 1. 调用公共有参方法
    Method addMethod = clazz.getMethod("add", int.class, int.class);
    Object sum = addMethod.invoke(calc, 10, 20);
    System.out.println("执行 add 方法结果: " + sum);

    // 2. 调用私有无参方法
    Method statusMethod = clazz.getDeclaredMethod("getInternalStatus");
    statusMethod.setAccessible(true);
    Object status = statusMethod.invoke(calc);
    System.out.println("执行私有方法结果: " + status);
  }
}
java
class Calculator {
  public int add(int a, int b) {
    return a + b;
  }

  private String getInternalStatus() {
    return "READY";
  }
}

反射获取构造器 ​

  • Constructor<?>[] getConstructors():(),获取公共构造器数组。返回包含所有 public 修饰的构造方法的 Constructor 数组。

  • Constructor<?>[] getDeclaredConstructors():(),获取所有声明构造器数组。返回当前类显式声明的所有构造方法的 Constructor 数组(包含 private、protected、包级私有及 public)。

  • Constructor<T> getConstructor():(Class<?>... parameterTypes),获取指定公共构造器。根据形参类型列表,精准匹配并返回特定的 public 构造方法对象。

  • Constructor<T> getDeclaredConstructor():(Class<?>... parameterTypes),获取指定声明构造器。根据形参类型列表,精准匹配并返回本类显式声明的特定构造方法对象(包含私有构造器)。

  • Constructor<?> getEnclosingConstructor():(),获取外围构造器。若当前 Class 对象表示构造函数内部定义的本地类(Local Class)或匿名内部类,则返回包含该类声明的底层 Constructor 对象;否则返回 null。

注意事项:

  1. getConstructors() 与 getConstructor() 仅能获取 public 作用域的构造方法。若目标构造方法为 private 或包默认权限,调用 getConstructor() 会直接抛出 NoSuchMethodException。
  2. 形参匹配严格区分基本类型与其包装类。例如,若构造方法形参为 int,匹配时必须传入 int.class(或 Integer.TYPE)。如果传入 Integer.class,反射引擎不会自动拆箱,将导致检索失败抛出 NoSuchMethodException。
  3. 构造方法不具备继承性。子类的 Class 对象无法获取父类中定义的构造方法。
java
import java.lang.reflect.Constructor;

public class ClassConstructorLookupDemo {
  public static void main(String[] args) throws Exception {
    Class<SampleBean> clazz = SampleBean.class;

    // 1. 获取所有 public 构造器
    Constructor<?>[] publicConstructors = clazz.getConstructors();
    System.out.println("Public 构造器数量: " + publicConstructors.length);

    // 2. 获取本类显式声明的所有构造器(含 private)
    Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
    System.out.println("Declared 构造器数量: " + declaredConstructors.length);

    // 3. 精准获取指定 public 带参构造器
    Constructor<SampleBean> publicParamConstructor = clazz.getConstructor(String.class);
    System.out.println("获取到公共单参构造器: " + publicParamConstructor);

    // 4. 精准获取指定 private 构造器
    Constructor<SampleBean> privateConstructor = clazz.getDeclaredConstructor(String.class, int.class);
    System.out.println("获取到私有双参构造器: " + privateConstructor);

    // 5. 探查本地类的外围构造器 (getEnclosingConstructor)
    class InConstructorClass {}
    Constructor<?> enclosing = InConstructorClass.class.getEnclosingConstructor();
    System.out.println("main 方法内本地类的外围构造器: " + enclosing); // 输出 null
  }
}
java
class SampleBean {
  public String name;
  private int score;

  // 1. 公共无参构造
  public SampleBean() {}

  // 2. 公共单参构造
  public SampleBean(String name) {
    this.name = name;
  }

  // 3. 私有双参构造
  private SampleBean(String name, int score) {
    this.name = name;
    this.score = score;
  }

  public void createLocalClass() {
    // 构造方法内部的本地类
    class LocalInnerClass {
      public LocalInnerClass() {
        // 用于演示外围构造器获取
      }
    }
  }
}

反射获取注解与泛型元数据 ​

  • <A Annotation extends> A getAnnotation():(Class<A> annotationClass),获取指定注解。若存在指定类型的注解,则返回该注解对象,否则返回 null。

  • Annotation[] getAnnotations():(),获取所有注解。返回当前元素上存在的所有注解(包括继承自父类的 @Inherited 注解)。

  • boolean isAnnotationPresent():(Class<? extends Annotation> annotationClass),判断注解是否存在。检查指定类型的注解是否存在于当前 Class 上。

  • Type getGenericSuperclass():(),获取带泛型参数的父类。返回带有实际泛型实参的父类 Type,用于在运行时提取继承链路中的泛型类型。

注意事项:

  1. 只有保留策略定义为 @Retention(RetentionPolicy.RUNTIME) 的注解,才能通过 Class 反射 API 在运行期读取。
  2. 由于 Java 存在泛型擦除机制,直接实例化 ArrayList<String> 无法通过 Class 获取 String;仅当通过显式子类继承指定泛型(如 class MyList extends ArrayList<String>)时,才能通过 getGenericSuperclass() 提取实际泛型参数。
java
import java.lang.annotation.*;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;

public class AnnotationGenericDemo {
  public static void main(String[] args) {
    Class<UserRepository> clazz = UserRepository.class;

    // 1. 读取类上的注解
    if (clazz.isAnnotationPresent(Table.class)) {
      Table table = clazz.getAnnotation(Table.class);
      System.out.println("表注解名称: " + table.name());
    }

    // 2. 解析泛型父类的实际类型参数
    Type genericSuperclass = clazz.getGenericSuperclass();
    if (genericSuperclass instanceof ParameterizedType) {
      ParameterizedType pt = (ParameterizedType) genericSuperclass;
      Type[] actualTypeArguments = pt.getActualTypeArguments();
      System.out.println("父类泛型实参类型: " + actualTypeArguments[0].getTypeName());
    }
  }
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Table {
  String name();
}

class BaseRepository<T> {}

@Table(name = "t_user")
class UserRepository extends BaseRepository<String> {}

其他 API ​

API: Constructor ​

对象实例化与执行 ​

  • T newInstance():(Object... initargs),实例化对象。通过调用此构造方法创建目标类的实例,自动执行构造方法体中的初始化逻辑。

  • Class<T> getDeclaringClass():(),获取声明类。返回表示声明此构造方法的类或接口的 Class 对象。

注意事项:

  1. 调用 newInstance(Object... initargs) 时,若构造方法内部抛出未捕获的异常,JVM 会将其统一包装为 InvocationTargetException 抛出,真实业务异常需通过 e.getCause() 提取。
  2. Java 语言规范严格禁止通过反射创建枚举(Enum)实例。即使通过 setAccessible(true) 打破访问限制,对枚举构造方法调用 newInstance() 仍会强行抛出 IllegalArgumentException: Cannot reflectively create enum objects。
java
import java.lang.reflect.Constructor;

public class InstantiationDemo {
  public static void main(String[] args) throws Exception {
    Class<TargetBean> clazz = TargetBean.class;

    // 获取公有带参构造方法
    Constructor<TargetBean> constructor = clazz.getDeclaredConstructor(String.class);

    // 调用 newInstance 传入实际实参进行实例化
    TargetBean bean = constructor.newInstance("Reflective Object");
    bean.printName();

    // 获取声明该构造方法的宿主 Class
    Class<TargetBean> declaringClass = constructor.getDeclaringClass();
    System.out.println("宿主类名: " + declaringClass.getName());
  }
}

class TargetBean {
  private String name;

  public TargetBean(String name) {
    this.name = name;
  }

  public void printName() {
    System.out.println("Bean Name: " + this.name);
  }
}

参数与异常元数据解析 ​

  • Class<?>[] getParameterTypes():(),获取形参类型数组。按声明顺序返回表示当前构造方法各形参类型的 Class 对象数组。

  • Parameter[] getParameters():(),获取形参元数据数组。返回表示当前构造方法所有形参的 Parameter 对象数组(JDK 8+)。

  • int getParameterCount():(),获取形参数量。返回当前构造方法的参数个数。

  • Class<?>[] getExceptionTypes():(),获取声明抛出的异常类型。返回当前构造方法声明抛出的所有受检异常类型的 Class 数组。

  • boolean isVarArgs():(),判断是否为可变参数构造方法。如果该构造方法声明接受可变数量的参数,则返回 true。

注意事项:

  1. 使用 getParameters() 获取形参具体名称(如 parameter.getName())时,默认情况下编译生成的字节码参数名为 arg0, arg1 等虚拟名称。若需保留源代码中的真实变量名,必须在 javac 编译命令中显式添加 -parameters 选项。
java
import java.lang.reflect.Constructor;
import java.lang.reflect.Parameter;

public class MetadataDemo {
  public static void main(String[] args) throws Exception {
    Class<?> clazz = ComplexService.class;
    Constructor<?> constructor = clazz.getDeclaredConstructor(String.class, int.class, String[].class);

    System.out.println("形参总数: " + constructor.getParameterCount());
    System.out.println("是否包含可变参数: " + constructor.isVarArgs());

    // 解析参数类型
    Class<?>[] paramTypes = constructor.getParameterTypes();
    for (int i = 0; i < paramTypes.length; i++) {
      System.out.println("参数 " + i + " 类型: " + paramTypes[i].getSimpleName());
    }

    // 解析 Parameter 元数据
    Parameter[] parameters = constructor.getParameters();
    for (Parameter p : parameters) {
      System.out.println("形参名称: " + p.getName() + " | 类型: " + p.getType().getTypeName());
    }

    // 解析声明的抛出异常
    Class<?>[] exceptionTypes = constructor.getExceptionTypes();
    for (Class<?> ex : exceptionTypes) {
      System.out.println("声明异常: " + ex.getName());
    }
  }
}

class ComplexService {
  public ComplexService(String configPath, int timeout, String... tags) throws IllegalArgumentException, NullPointerException {
    // 示例构造函数
  }
}

访问权限控制与修饰符查询 ​

  • void setAccessible():(boolean flag),设置可访问标志。将此反射对象的 accessible 标识设置为指定布尔值,用于绕过 Java 访问控制校验(强行调用私有构造方法)。

  • boolean canAccess():(Object obj),校验模块与可访问性。检查当前调用者是否能够在不破坏强封装的前提下访问该构造方法(JDK 9+)。

  • int getModifiers():(),获取修饰符整数值。返回编码了当前构造方法访问修饰符(如 default:0、 public:1、private:2、static:8、final:16)的位掩码整数。

注意事项:

  1. 在 Java 9 及以上版本的模块化系统(JPMS)中,如果构造方法所在类所属的包未向当前模块开放(opens),调用 setAccessible(true) 会强行抛出 InaccessibleObjectException 异常。
  2. 对私有构造方法设置 setAccessible(true) 会破坏单例模式的安全性。通常建议在单例类的私有构造方法内部增加“已存在实例则抛出异常”的校验逻辑进行抵御。
java
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;

public class AccessControlDemo {
  public static void main(String[] args) throws Exception {
    Class<PrivateSingleton> clazz = PrivateSingleton.class;

    // 获取私有无参构造方法
    Constructor<PrivateSingleton> constructor = clazz.getDeclaredConstructor();

    // 查询修饰符信息
    int modifiers = constructor.getModifiers();
    System.out.println("修饰符掩码: " + modifiers);
    System.out.println("修饰符文本: " + Modifier.toString(modifiers));
    System.out.println("是否为 private: " + Modifier.isPrivate(modifiers));

    // 检查 JDK 9+ 的模块访问限制
    System.out.println("当前上下文可访问状态: " + constructor.canAccess(null));

    // 破译访问控制权限
    constructor.setAccessible(true);
    PrivateSingleton newInstance = constructor.newInstance();
    System.out.println("突破私有权限实例化对象成功: " + (newInstance != null));
  }
}

class PrivateSingleton {
  private static final PrivateSingleton INSTANCE = new PrivateSingleton();

  private PrivateSingleton() {
    System.out.println("私有构造方法被成功调用");
  }
}

注解元数据提取 ​

  • <A Annotation extends> A getAnnotation():(Class<A> annotationClass),获取指定注解。若存在指定类型的注解,则返回该注解的动态代理实例,否则返回 null。

  • Annotation[][] getParameterAnnotations():(),获取形参注解。返回一个二维数组,按形参声明顺序和参数上的注解列表组织注解实例。

注意事项:

  1. 读取构造方法或其形参上的注解时,目标注解必须显式修饰为 @Retention(RetentionPolicy.RUNTIME),否则在运行期通过反射调用时注解数据将被抛弃而返回 null。
java
import java.lang.annotation.*;
import java.lang.reflect.Constructor;

public class AnnotationExtractionDemo {
  public static void main(String[] args) throws Exception {
    Class<OrderService> clazz = OrderService.class;
    Constructor<OrderService> constructor = clazz.getDeclaredConstructor(String.class, String.class);

    // 提取构造方法上的注解
    if (constructor.isAnnotationPresent(InjectConstructor.class)) {
      InjectConstructor inject = constructor.getAnnotation(InjectConstructor.class);
      System.out.println("构造方法标注了 @InjectConstructor 注解");
    }

    // 提取形参上的二维注解数组
    Annotation[][] paramAnnotations = constructor.getParameterAnnotations();
    for (int i = 0; i < paramAnnotations.length; i++) {
      System.out.println("参数位置 [" + i + "] 上的注解:");
      for (Annotation anno : paramAnnotations[i]) {
        if (anno instanceof Qualifier) {
          Qualifier q = (Qualifier) anno;
          System.out.println("  -> @Qualifier 值: " + q.value());
        }
      }
    }
  }
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.CONSTRUCTOR)
@interface InjectConstructor {}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@interface Qualifier {
  String value();
}

class OrderService {
  @InjectConstructor
  public OrderService(@Qualifier("mysqlDao") String daoName, @Qualifier("redisCache") String cacheName) {
  }
}

API: Field ​

属性元数据获取 ​

  • String getName():(),获取属性名称。返回该 Field 对象所表示属性的简单名称。

  • Class<?> getType():(),获取属性类型。返回表示该属性声明类型的 Class 对象。

  • Type getGenericType():(),获取带泛型信息的属性类型。返回表示该属性声明类型的 Type 对象,若属性显式声明了泛型实参,可用于提取泛型参数信息。

  • int getModifiers():(),获取访问修饰符位掩码。返回编码了当前属性修饰符(如 default:0、 public:1、private:2、static:8、final:16)的整数掩码。

  • Class<?> getDeclaringClass():(),获取声明属性的宿主类。返回声明当前 Field 对象的 Class 对象。

  • boolean isEnumConstant():(),判断是否为枚举常量。若该属性表示枚举类型的元素,则返回 true。

  • boolean isSynthetic():(),判断是否为合成属性。若该属性是由编译器自动生成的(例如内部类持有外部类引用的 this$0),则返回 true。

注意事项:

  1. getType() 仅返回编译期擦除泛型后的裸类型(Raw Type)。若需提取泛型类型(如 List<String> 中的 String),必须调用 getGenericType() 并向下转型为 ParameterizedType 进行提取。
  2. getModifiers() 返回的位掩码需搭配 java.lang.reflect.Modifier 工具类(如 Modifier.isStatic(int)、Modifier.isFinal(int))解析为具体的字符串描述。
java
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;

public class FieldMetadataDemo {
  public static void main(String[] args) throws Exception {
    Class<Container> clazz = Container.class;

    // 1. 解析静态公共字段 VERSION
    Field versionField = clazz.getField("VERSION");
    System.out.println("属性名称: " + versionField.getName());
    System.out.println("属性类型: " + versionField.getType().getName());
    System.out.println("修饰符: " + Modifier.toString(versionField.getModifiers()));
    System.out.println("声明类: " + versionField.getDeclaringClass().getSimpleName());

    // 2. 解析私有泛型字段 items
    Field itemsField = clazz.getDeclaredField("items");
    System.out.println("\n属性名称: " + itemsField.getName());
    Type genericType = itemsField.getGenericType();
    System.out.println("带泛型类型: " + genericType.getTypeName());
    if (genericType instanceof ParameterizedType) {
      ParameterizedType pType = (ParameterizedType) genericType;
      System.out.println("泛型实参类型: " + pType.getActualTypeArguments()[0].getTypeName());
    }

    // 3. 检查枚举常量与合成属性
    Field enumField = Container.Status.class.getField("ACTIVE");
    System.out.println("\n是否枚举常量: " + enumField.isEnumConstant());
    System.out.println("是否合成属性: " + versionField.isSynthetic());
  }
}

class Container<T> {
  public static final String VERSION = "1.0";
  private List<String> items;

  enum Status { ACTIVE, INACTIVE }
}

属性值的读取与修改 ​

  • Object get():(Object obj),获取属性值。返回指定对象实例上当前 Field 表示的属性值。

  • void set():(Object obj, Object value),设置属性值。将指定对象实例上当前 Field 表示的属性修改为新值。

  • int getInt():(Object obj),获取原生 int 属性值。提取指定对象上 int 或可自动扩宽的基本类型属性的值。

  • void setInt():(Object obj, int i),设置原生 int 属性值。修改指定对象上 int 类型属性的值。

  • boolean getBoolean():(Object obj),获取原生 boolean 属性值。提取指定对象上 boolean 类型属性的值。

  • void setBoolean():(Object obj, boolean z),设置原生 boolean 属性值。修改指定对象上 boolean 类型属性的值。

注意事项:

  1. 操作实例属性时,传入的 obj 必须是该属性所属类(或其子类)的有效实例,否则抛出 IllegalArgumentException;若操作的是静态(static)属性,obj 参数可传 null。
  2. 为基本数据类型字段赋值时,若类型不匹配(例如将 String 传给 int 属性),或者向对象引用字段赋予非兼容类型,会抛出 IllegalArgumentException;若对基本类型字段传入 null,会抛出 NullPointerException。
java
import java.lang.reflect.Field;

class Player {
  public static String gameTitle = "Java Quest";
  public String nickname;
  public int level;
  public boolean isActive;

  public Player(String nickname, int level, boolean isActive) {
    this.nickname = nickname;
    this.level = level;
    this.isActive = isActive;
  }
}

public class FieldValueDemo {
  public static void main(String[] args) throws Exception {
    Player player = new Player("Hero", 10, true);
    Class<Player> clazz = Player.class;

    // 1. 操作静态字段 (传 null)
    Field gameTitleField = clazz.getField("gameTitle");
    System.out.println("原游戏标题: " + gameTitleField.get(null));
    gameTitleField.set(null, "Java Quest II");
    System.out.println("修改后游戏标题: " + Player.gameTitle);

    // 2. 读取与修改对象引用字段
    Field nicknameField = clazz.getField("nickname");
    System.out.println("原昵称: " + nicknameField.get(player));
    nicknameField.set(player, "SuperHero");

    // 3. 读取与修改基本数据类型字段
    Field levelField = clazz.getField("level");
    Field activeField = clazz.getField("isActive");

    System.out.println("原等级: " + levelField.getInt(player));
    levelField.setInt(player, 99);

    System.out.println("原状态: " + activeField.getBoolean(player));
    activeField.setBoolean(player, false);

    System.out.println("\n修改后玩家信息: " + player.nickname + " | Level: " + player.level + " | Active: " + player.isActive);
  }
}

访问控制与反射阻断突破 ​

  • void setAccessible():(boolean flag),设置可访问标识。突破 Java 语言的访问权限控制(强行读取或修改私有 private 属性)。

  • boolean canAccess():(Object obj),校验可访问权限。检查当前调用者上下文是否具有直接访问该属性的权限(JDK 9+)。

  • boolean trySetAccessible():(),尝试设置可访问标识。安全地尝试突破访问限制,成功返回 true,若受限于模块隔离或安全性检查失败则返回 false 而不会抛出异常(JDK 9+)。

注意事项:

  1. 在 Java 9+ 模块化系统(JPMS)中,若目标类所属的包未向当前模块开放(opens),即使调用 setAccessible(true) 也会强行抛出 InaccessibleObjectException 异常。
  2. 修改 static final 常量属性时,JVM 在编译期可能已将基本类型或字符串常量进行了内联优化(Inline Optimization),即使通过反射成功修改了 Field 的内存值,直接访问常量的代码仍可能返回旧的内联字面量。
java
import java.lang.reflect.Field;

class Account {
  private String accountNumber = "ACC-9988";
  private double balance = 5000.50;
}

public class FieldAccessibilityDemo {
  public static void main(String[] args) throws Exception {
    Account account = new Account();
    Class<Account> clazz = Account.class;

    Field accNumField = clazz.getDeclaredField("accountNumber");
    Field balanceField = clazz.getDeclaredField("balance");

    // 检查默认访问权限 (私有属性,默认不可直接 access)
    System.out.println("当前 accountNumber 可访问性: " + accNumField.canAccess(account));

    // 使用 trySetAccessible 安全尝试开启权限 (JDK 9+)
    if (accNumField.trySetAccessible()) {
      System.out.println("成功突破访问权限,accountNumber = " + accNumField.get(account));
    }

    // 使用 setAccessible 强行突破访问私有 double 字段
    balanceField.setAccessible(true);
    System.out.println("原余额: " + balanceField.get(account));
    balanceField.set(account, 10000.00);
    System.out.println("修改后余额: " + balanceField.get(account));
  }
}

属性注解解析 ​

  • <A Annotation extends> A getAnnotation():(Class<A> annotationClass),获取指定类型的注解。若当前属性标注了指定类型的注解则返回该注解实例,否则返回 null。

  • Annotation[] getAnnotations():(),获取所有注解。返回当前属性上存在的所有注解数组(包含继承的注解)。

  • Annotation[] getDeclaredAnnotations():(),获取显式声明的注解。返回直接显式声明在该属性上的所有注解数组。

  • boolean isAnnotationPresent():(Class<? extends Annotation> annotationClass),判断注解是否存在。检查指定类型的注解是否存在于当前属性上。

注意事项:

  1. 能通过 Field 反射提取的注解,其保留策略必须明确标注为 @Retention(RetentionPolicy.RUNTIME)。若保留策略为 SOURCE 或 CLASS,编译后注解元数据将在运行期被丢弃,反射提取时将返回 null。
java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Annotation;
import java.lang.reflect.Field;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
@interface Column {
  String name();
  boolean primaryKey() default false;
}

class Product {
  @Column(name = "product_id", primaryKey = true)
  private Long id;

  @Column(name = "product_title")
  private String title;
}

public class FieldAnnotationDemo {
  public static void main(String[] args) throws Exception {
    Class<Product> clazz = Product.class;

    for (Field field : clazz.getDeclaredFields()) {
      // 判断是否标注了 @Column 注解
      if (field.isAnnotationPresent(Column.class)) {
        Column column = field.getAnnotation(Column.class);
        System.out.println("属性: " + field.getName() + " -> 映射数据库列: " + column.name() + " | 是否主键: " + column.primaryKey());
      }

      // 获取显式声明的所有注解
      Annotation[] declaredAnnotations = field.getDeclaredAnnotations();
      System.out.println("属性 " + field.getName() + " 声明注解数量: " + declaredAnnotations.length);
    }
  }
}

API: Method ​

构造方法与获取途径 ​

  • Method Method():(Class<?> declaringClass, String name, Class<?>[] parameterTypes, Class<?> returnType, Class<?>[] checkedExceptions, int modifiers, int slot, String signature, byte[] annotations, byte[] parameterAnnotations, byte[] annotationDefault),包级私有构造方法。JVM 内部在加载和解析类元数据时自动调用,外部代码无法通过 new 显式实例化 Method。

注意事项:

  1. Method 的构造方法受 JVM 包级别访问权限保护,开发者必须使用 Class.getMethod()、Class.getDeclaredMethod() 或其复数形式 API 提取 Method 实例。
java
import java.lang.reflect.Method;

public class MethodConstructorDemo {
  public static void main(String[] args) throws Exception {
    // 外部仅能通过 Class 对象获取 Method 实例,无法显式 new Method(...)
    Class<String> clazz = String.class;
    Method substringMethod = clazz.getMethod("substring", int.class, int.class);

    System.out.println("成功通过 Class 检索获取 Method 对象: " + substringMethod);
  }
}

方法执行与反射特性 ​

  • Object invoke():(Object obj, Object... args),反射调用目标方法。对指定的对象实例执行当前 Method 所表示的方法,并传入实参列表。若为静态方法,obj 可传 null。

  • Object getDefaultValue():(),获取注解元素的默认值。当当前 Method 表示注解类型的成员时,返回其声明的默认值(非注解类型返回 null)。

  • boolean isBridge():(),判断是否为桥接方法。若该方法是由编译器为了支持泛型类型擦除而自动生成的桥接方法(Bridge Method),则返回 true。

  • boolean isDefault():(),判断是否为接口默认方法。若当前方法是接口中用 default 关键字修饰的实例方法,则返回 true。

  • boolean isVarArgs():(),判断是否声明可变参数。若当前方法最后一个形参为可变参数(Type...),则返回 true。

  • boolean isSynthetic():(),判断是否为合成方法。若当前方法是由编译器内部自动生成的(如内部类访问外部私有成员时生成的 access$000 方法),则返回 true。

注意事项:

  1. 调用 invoke() 时,若被调用的底层方法内部抛出未捕获的异常,JVM 并不会直接抛出原异常,而是统一包装为 java.lang.reflect.InvocationTargetException。业务逻辑必须通过 e.getCause() 提取真实的异常原因。
  2. 反射执行无参方法或静态方法时,实参列表可传空数组或 null。对基本类型返回值(如 int),invoke() 会自动将其装箱为对应的包装类(如 Integer)。
java
import java.lang.reflect.Method;

interface Calculator {
  default int add(int a, int b) {
    return a + b;
  }
}

class AdvancedCalculator implements Calculator {
  public void printLogs(String message, Object... details) {}
}

public class MethodExecutionDemo {
  public static void main(String[] args) throws Exception {
    Calculator calc = new AdvancedCalculator();

    // 1. 验证接口默认方法 (isDefault) 与反射执行 (invoke)
    Method addMethod = Calculator.class.getMethod("add", int.class, int.class);
    System.out.println("是否为接口默认方法: " + addMethod.isDefault());
    Object sum = addMethod.invoke(calc, 15, 25);
    System.out.println("反射调用 add() 计算结果: " + sum);

    // 2. 检查可变参数方法 (isVarArgs)
    Method logMethod = AdvancedCalculator.class.getMethod("printLogs", String.class, Object[].class);
    System.out.println("是否包含可变参数: " + logMethod.isVarArgs());

    // 3. 检查合成与桥接状态 (isSynthetic / isBridge)
    System.out.println("是否为合成方法: " + logMethod.isSynthetic());
    System.out.println("是否为桥接方法: " + logMethod.isBridge());
  }
}

参数、返回值与异常元数据 ​

  • Class<?> getReturnType():(),获取返回值擦除类型。返回表示方法返回值类型的 Class 对象。

  • Type getGenericReturnType():(),获取带泛型信息的返回值类型。返回包含泛型实参的 Type 对象。

  • Class<?>[] getParameterTypes():(),获取形参擦除类型数组。按声明顺序返回参数类型的 Class 数组。

  • Type[] getGenericParameterTypes():(),获取带泛型信息的形参类型数组。返回包含参数泛型信息的 Type 数组。

  • int getParameterCount():(),获取形参个数。返回方法的形参数量(比 getParameterTypes().length 更高效,无需创建数组)。

  • Parameter[] getParameters():(),获取形参对象元数据数组。返回表示各形参详细属性的 Parameter 对象数组(JDK 8+)。

  • Class<?>[] getExceptionTypes():(),获取声明抛出的异常类型数组。返回当前方法在 throws 子句中显式声明的所有受检异常的 Class 数组。

注意事项:

  1. getReturnType() 与 getParameterTypes() 返回的是类型擦除后的原生类(Raw Class)。若要提取泛型参数(如 List<User> 中的 User),必须调用 getGenericReturnType() 或 getGenericParameterTypes(),并强转为 ParameterizedType 解析。
  2. getParameters() 获取形参名称(parameter.getName())时,若编译时未启用 -parameters 选项,获取到的名称将为虚拟的 arg0, arg1。
java
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;

public class MethodMetadataDemo {
  public static void main(String[] args) throws Exception {
    Class<DataService> clazz = DataService.class;
    Method method = clazz.getMethod("fetchData", int.class, String.class);

    System.out.println("形参总数: " + method.getParameterCount());

    // 解析泛型返回值
    Type returnGenericType = method.getGenericReturnType();
    if (returnGenericType instanceof ParameterizedType) {
      ParameterizedType pType = (ParameterizedType) returnGenericType;
      System.out.println("返回值泛型实参: " + pType.getActualTypeArguments()[0].getTypeName());
    }

    // 解析受检异常列表
    for (Class<?> ex : method.getExceptionTypes()) {
      System.out.println("声明抛出的异常: " + ex.getSimpleName());
    }
  }
}

class DataService {
  public List<String> fetchData(int id, String token) throws IllegalArgumentException, NullPointerException {
    return List.of("data");
  }
}

访问权限控制与修饰符查询 ​

  • void setAccessible():(boolean flag),设置可访问标识。突破 Java 访问控制权限校验,允许反射调用 private 或包私有方法。

  • boolean canAccess():(Object obj),校验可访问权限。检查当前调用者上下文是否具有执行该方法的直接权限(JDK 9+)。

  • boolean trySetAccessible():(),尝试设置可访问标识。安全地尝试开启访问权限,成功返回 true,失败(如受 JPMS 模块封锁)返回 false 而不抛出异常(JDK 9+)。

  • int getModifiers():(),获取修饰符位掩码。返回编码了当前方法修饰符(如 default:0、 public:1、private:2、static:8、final:16、synchronized)的整数掩码。

  • Class<?> getDeclaringClass():(),获取声明该方法的宿主 Class。返回定义该 Method 的类或接口的 Class 对象。

注意事项:

  1. 在 JDK 9 及以上版本的 Java 模块化系统(JPMS)中,若目标类所属的包未向当前模块开放(opens),即使调用 setAccessible(true) 也会强行抛出 java.lang.reflect.InaccessibleObjectException 异常。
  2. getModifiers() 返回的整数需配合 java.lang.reflect.Modifier 的静态方法(如 Modifier.isStatic(int))进行解译。
java
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

class InternalSecurity {
  private static void performCheck() {
    System.out.println("[安全系统] 私有安全检查执行完毕");
  }
}

public class AccessControlDemo {
  public static void main(String[] args) throws Exception {
    Class<InternalSecurity> clazz = InternalSecurity.class;
    Method privateMethod = clazz.getDeclaredMethod("performCheck");

    // 检查修饰符位掩码
    int modifiers = privateMethod.getModifiers();
    System.out.println("修饰符描述: " + Modifier.toString(modifiers));
    System.out.println("是否为 private: " + Modifier.isPrivate(modifiers));
    System.out.println("是否为 static: " + Modifier.isStatic(modifiers));

    // 检查访问控制
    System.out.println("当前上下文可访问状态: " + privateMethod.canAccess(null));

    // 安全地尝试突破访问限制
    if (privateMethod.trySetAccessible()) {
      privateMethod.invoke(null); // 静态私有方法调用,obj 传 null
    }
  }
}

注解元数据提取 ​

  • <A Annotation extends> A getAnnotation():(Class<A> annotationClass),获取指定类型的注解。若当前方法标注了该类型的注解则返回其代理对象,否则返回 null。

  • Annotation[] getAnnotations():(),获取所有注解。返回当前方法上存在的所有注解数组(包含继承的注解)。

  • Annotation[] getDeclaredAnnotations():(),获取显式声明的注解。返回直接声明在当前方法上的注解数组。

  • Annotation[][] getParameterAnnotations():(),获取形参二维注解数组。按参数顺序及每个参数上的注解列表组织返回二维数组。

  • boolean isAnnotationPresent():(Class<? extends Annotation> annotationClass),判断注解是否存在。检查当前方法上是否存在指定类型的注解。

注意事项:

  1. 能通过 Method 反射读取的注解,其保留策略必须显式标注为 @Retention(RetentionPolicy.RUNTIME)。若保留策略为 CLASS 或 SOURCE,运行期通过 Method 提取时将返回 null。
java
import java.lang.annotation.*;
import java.lang.reflect.Method;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Mapping {
  String path();
}

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@interface Param {
  String value();
}

class ApiController {
  @Mapping(path = "/api/v1/user")
  public void getUser(@Param("user_id") String id) {}
}

public class MethodAnnotationDemo {
  public static void main(String[] args) throws Exception {
    Method method = ApiController.class.getMethod("getUser", String.class);

    // 1. 方法级注解解析
    if (method.isAnnotationPresent(Mapping.class)) {
      Mapping mapping = method.getAnnotation(Mapping.class);
      System.out.println("路由映射路径: " + mapping.path());
    }

    // 2. 形参级二维注解解析
    Annotation[][] paramAnnotations = method.getParameterAnnotations();
    for (int i = 0; i < paramAnnotations.length; i++) {
      for (Annotation anno : paramAnnotations[i]) {
        if (anno instanceof Param) {
          Param p = (Param) anno;
          System.out.println("形参索引 [" + i + "] 绑定的请求参数名: " + p.value());
        }
      }
    }
  }
}

类加载 ​

Java 中的类加载机制(Class Loading) 是指 JVM 将描述类数据的 .class 字节码文件从磁盘或网络加载到内存,并对数据进行校验、解析与初始化,最终形成可以被 JVM 直接使用的 java.lang.Class 对象的过程。

静态/动态加载 ​

Java 中的类加载根据触发时机与依赖关系,分为静态加载(Static Loading) 与动态加载(Dynamic Loading) 两种方式。这是 Java 实现编译期安全与运行期扩展特性的底层机制。

  • 静态加载:在编译期就明确确定了类依赖关系。程序在启动或编译时,JVM 会校验所有静态引用的类;若缺少相关的 .class 文件,程序在编译期或启动期就会报错。
  • 动态加载:在运行期根据程序逻辑的执行结果按需加载类。只有当代码执行到加载逻辑(如 Class.forName())时,JVM 才会去寻找并加载对应的字节码;若类不存在,只会在运行到该代码行时抛出异常。

静态加载 ​

静态加载是 Java 最基础、最常见的类型加载方式。

触发时机 ​

以下语法会触发静态加载:

  • 使用 new 关键字创建对象实例(如 User user = new User())。
  • 访问类的静态成员变量或静态方法(如 Math.sqrt())。
  • 子类被加载时,其父类与实现的接口会被静态加载。
机制特点 ​
  • 编译期强依赖:在将源代码编译为 .class 文件时,编译器必须能找到所有静态引用的类,否则无法通过编译。
  • 启动即校验:JVM 启动或主类加载时,需要将所有关联的静态类准备就绪。如果运行时丢失了某个 .class 文件,JVM 启动阶段会直接抛出 NoClassDefFoundError。

动态加载 ​

动态加载是 Java 能够被视为“准动态语言”的核心机制,允许程序在不重新编译的前提下引入新功能。

实现途径 ​
  • Class.forName("全类名"):根据字符串名称动态加载类并默认执行静态初始化块。
  • ClassLoader.loadClass("全类名"):使用指定的类加载器装载类,但不会立即进行初始化。
  • 反射与动态代理:利用 java.lang.reflect API 在运行期解析并生成代理类。
机制特点 ​
  • 解耦编译期依赖:主程序在编译阶段不需要依赖被动态加载的类。即使目标 .class 文件完全不存在,主程序也能顺利编译通过。

  • 按需加载与弹性拓展:只有在代码实际运行到加载语句时,JVM 才会试图将类加载入内存。如果找不到指定的类,会在运行期抛出 ClassNotFoundException。

    java
    // 静态加载:如果 Shape 及其子类缺失,编译直接报错
    Shape shape = new Circle();
    
    // 动态加载:编译期仅依赖字符串,运行期根据参数动态决定加载哪一个具体类
    String className = args[0]; // 例如 "com.example.Square"
    Class<?> clazz = Class.forName(className);
    Shape dynamicShape = (Shape) clazz.getDeclaredConstructor().newInstance();

差异对比 ​

下表展现了静态加载与动态加载在各个维度的差异:

比较维度静态加载动态加载
确定时机编译期确定类型关系运行期动态确定类型关系
编译期依赖强依赖(必须包含被引用类的 .class 文件)弱依赖(只需保证字符串或通用接口存在)
触发方式new 关键字、直接引用静态成员、继承Class.forName()、ClassLoader.loadClass()
异常类型编译报错,或启动期 NoClassDefFoundError运行时触发 ClassNotFoundException
执行性能高(JIT 易于优化与内联)较低(包含动态检索与权限校验)
灵活性低极高(支持热插拔与插件机制)

示例:静态加载 vs 动态加载

java
public class MainDemo {
  public static void main(String[] args) {
    String inputType = args.length > 0 ? args[0] : "Word";

    // ==================== 1. 静态加载 ====================
    if ("Word".equalsIgnoreCase(inputType)) {
      // 通过 new 关键字直接引用 Word 类
      Word word = new Word();
      word.start();
    }

    // ==================== 2. 动态加载 ====================
    if ("Excel".equalsIgnoreCase(inputType)) {
      try {
        // 通过类名字符串动态装载 Excel 类
        Class<?> clazz = Class.forName("Excel");
        OfficeApp app = (OfficeApp) clazz.getDeclaredConstructor().newInstance();
        app.start();
      } catch (ClassNotFoundException e) {
        System.err.println("运行期异常:未找到对应的动态类文件!");
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
  }
}
java
// 统一功能接口
interface OfficeApp {
  void start();
}

// 静态加载的目标类
class Word implements OfficeApp {
  @Override
  public void start() {
    System.out.println("Word 启动成功!");
  }
}

// 动态加载的目标类
class Excel implements OfficeApp {
  @Override
  public void start() {
    System.out.println("Excel 启动成功!");
  }
}

通过分别对以上代码进行编译和运行期操作,可以清晰看出两种加载方式的本质差异:

  • 编译期表现差异:

    • 静态加载依赖:如果删掉 Word.java 文件直接编译 MainDemo.java,编译器会直接报错:找不到符号:类 Word。
    • 动态加载解耦:即使删掉 Excel.java 文件,只要保留 OfficeApp 接口,MainDemo.java 依然能够完美编译通过。因为程序在编译阶段只知道字符串 "Excel",不知道也不校验其对应类的存在。
  • 运行期缺失表现:

    假设将所有文件都成功编译为 .class 文件后,进行如下删除测试:

    • 删除 Word.class:
      • 运行 java MainDemo Excel(期望执行动态加载分支)。
      • 结果:程序依然可能直接抛出 java.lang.NoClassDefFoundError: Word 并中断程序。这是因为静态加载的类在方法被调用或验证时就会被 JVM 强制解析校验。
    • 删除 Excel.class:
      • 运行 java MainDemo Word(执行静态加载分支)。
      • 结果:程序能够正常运行并成功输出 "Word 启动成功!"。
      • 运行 java MainDemo Excel(执行动态加载分支)。
      • 结果:程序不会崩溃,而是捕获到 ClassNotFoundException,输出自定义的异常提示:"运行期异常:未找到对应的动态类文件!"。

应用场景 ​

JDBC 数据库驱动加载 ​

JDBC 规范使用动态加载来解除接口与各大数据库厂商实现类之间的强耦合:

java
// 动态加载 MySQL 驱动,无需在代码中硬编码引用 MySQL 的具体 Driver 类
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection(url, user, pass);

当切换为 Oracle 或 PostgreSQL 时,主程序代码无需修改,只需更改传入的字符串与对应的 JAR 包。

框架依赖注入与插件架构 ​
  • Spring 框架:Spring IOC 容器解析 XML 或 @Component 注解获取类名的字符串,利用动态加载将 Bean 装载入内存并实例化。
  • Web 容器与热部署:Tomcat 等 Web 服务器利用自定义类加载器实现不同 Web 应用的隔离与动态重载,更新代码时只需更新动态加载的模块,无需重启整个 JVM。

类加载过程 ​

类加载过程:类的生命周期从被加载到虚拟机内存开始,到卸载出内存为止,包含以下七个阶段:加载、验证、准备、解析、初始化、使用、卸载。其中,验证、准备、解析三个部分统称为链接(Linking)。

image-20260804223944378

加载 ​

加载(Loading) 是类加载过程的第一阶段,JVM 在此阶段完成三件事:

  • 获取二进制字节流:通过类的全限定名获取定义此类的二进制字节流(可来自本地磁盘 .class 文件、JAR 包、网络传输或动态生成如动态代理)。
  • 转化静态数据结构:将字节流所代表的静态存储结构转化为方法区(元空间 Metaspace)的运行时数据结构。
  • 生成 Class 对象:在堆内存中生成一个代表该类的 java.lang.Class 对象,作为访问元空间中这些数据的物理入口。

验证 ​

验证(Verification) 是链接阶段的第一步,目的是确保 .class 文件的字节流包含的信息符合《Java 虚拟机规范》,不会危害 JVM 自身安全:

  • 文件格式验证:检查魔数(0xCAFEBABE)、主次版本号等格式是否正确。
  • 元数据验证:进行语法和语义分析,如该类是否有父类、是否非法继承了 final 类。
  • 字节码验证:通过数据流分析和控制流分析,确定程序语义是合法、符合逻辑的。
  • 符号引用验证:校验符号引用中通过全限定名能否找到对应的类、方法和字段。

准备 ​

准备(Preparation) 阶段正式为类中定义的静态变量(static 修饰的变量)分配内存并设置零值(默认初始值):

  • 内存位置:JDK 8 之后,静态变量的内存分配随 Class 对象一同存放在堆内存中。

  • 零值设定:此时设定的值为数据类型的零值(如 0、false、null),而非代码中显式赋予的值。

    java
    // 在准备阶段,value 的值被设为 0,而非 123
    public static int value = 123;
    
    // 特例:如果是 static final 编译期常量,准备阶段就会被直接赋予显式值 123
    public static final int CONSTANT = 123;

解析 ​

解析(Resolution) 阶段是 JVM 将常量池内的符号引用(类名、字段名、方法名)替换为直接引用(内存地址) 的过程:

  • 符号引用:以一组符号来描述所引用的目标,与 JVM 内存布局无关。
  • 直接引用:可以直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄,必须与 JVM 内存布局相关。

解析主要针对类或接口、字段、类方法、接口方法等符号引用进行。

初始化 ​

初始化(Initialization) 是类加载过程的收尾阶段,也是开始执行类中编写的 Java 程序代码(字节码)的阶段。

  • <clinit>() 方法:编译器在此阶段自动收集类中所有静态变量的赋值动作和 static {} 静态代码块中的语句,合并产生 <clinit>() 方法并执行。
  • 线程安全保证:JVM 会保证一个类的 <clinit>() 方法在多线程环境下被正确地加锁和同步,确保每个类只会被初始化一次,每个类在内存中只有一个 Class 对象。

反射爆破操作 ​

获取 Class 对象 ​

获取 Class 对象的四种方式:在日常开发与框架设计中,最常用的获取 Class 对象的方式有以下四种:

类名字面量 .class ​

直接通过类名的 .class 属性获取。

java
// 语法:类名.class
Class<User> userClass = User.class;
Class<String> stringClass = String.class;
  • 适用场景:在编译期已知具体的类名称。常用于方法参数传递(如 Spring 的 getBean(User.class))。
  • 特性:性能最高且最安全,在编译期进行类型检查;不会触发类的初始化(即不会执行 static {} 静态代码块)。

对象实例 .getClass() ​

通过 java.lang.Object 类中的 getClass() 实例方法获取。

java
User user = new User();
// 语法:实例对象.getClass()
Class<? extends User> userClass = user.getClass();
  • 适用场景:在运行期已经拥有了该类的实例对象,用于确定对象的真实运行时类型(如处理多态场景)。
  • 特性:属于 Object 类的 public final 方法,要求对象不能为 null,否则会抛出 NullPointerException。

全类名字符串 Class.forName() ​

通过 Class 类的静态方法 forName(),传入类的全限定名(包名 + 类名)获取。

java
try {
  // 语法:Class.forName("包名.类名")
  Class<?> clazz = Class.forName("com.example.model.User");
} catch (ClassNotFoundException e) {
  e.printStackTrace();
}
  • 适用场景:类名写在配置文件或通过网络传输(如 JDBC 驱动加载 Class.forName("com.mysql.cj.jdbc.Driver"))。
  • 特性:
    • 默认会触发类的静态初始化(执行 static {} 代码块)。
    • 支持重载方法 Class.forName(name, initialize, classloader),可手动指定是否初始化及所用的类加载器。

类加载器 ClassLoader.loadClass() ​

通过获取具体的 ClassLoader 实例并调用其 loadClass() 方法加载目标类。

java
try {
  ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
  // 语法:classLoader.loadClass("包名.类名")
  Class<?> clazz = classLoader.loadClass("com.example.model.User");
} catch (ClassNotFoundException e) {
  e.printStackTrace();
}
  • 适用场景:自定义类加载路径、热插拔插件架构或跳过类的初始化。
  • 特性:仅将字节码装载入 JVM 并生成 Class 对象,不会对类进行链接和静态初始化(即不会执行静态代码块)。

特殊类型获取 ​

除了常规自定义类之外,基本数据类型、数组以及继承关系也有对应的获取方式:

基本类型与 void ​

基本数据类型(如 int, double 等)和 void 关键字同样有其对应的 Class 对象:

java
// 方式一:直接使用字面量
Class<Integer> intClass = int.class;
Class<Void> voidClass = void.class;

// 方式二:通过包装类的 TYPE 常量(与上面等价)
Class<Integer> typeClass = Integer.TYPE;
数组类型 ​

数组同样是 Object 的子类,具有独立的 Class 对象:

java
// 方式一:字面量形式
Class<String[]> stringArrayClass = String[].class;
Class<int[][]> twoDIntArrayClass = int[][].class;

// 方式二:通过 JVM 类型的Class.forName
Class<?> arrayClazz = Class.forName("[Ljava.lang.String;"); // String[] 的内部类型描述符
结构衍生获取 ​

如果已经拥有了一个 Class 对象,可以顺着类继承树查找相关类型的 Class:

java
Class<ArrayList> listClass = ArrayList.class;

// 获取直接父类的 Class 对象
Class<? super ArrayList> superClass = listClass.getSuperclass(); // AbstractList.class

// 获取实现的接口 Class 数组
Class<?>[] interfaces = listClass.getInterfaces();

方式差异对比 ​

下表对比了四种主要获取方式在执行细节上的区别:

获取方式是否触发类初始化检查时机需要实例典型场景
类名.class否编译期否类型安全传递、注解配置
obj.getClass()否(实例已初始化)运行期是多态下判断真实类型
Class.forName()是(默认)运行期否动态配置读取、JDBC 驱动加载
ClassLoader.loadClass()否运行期否插件化架构、自定义加载

创建实例 ​

在 Java 中,正常的对象创建通常使用 new 关键字完成。而通过反射机制(Reflection)创建实例,则允许程序在运行期根据类的名称或 Class 对象,动态地实例化对象。这是框架(如 Spring IOC、ORM 工具、RPC 框架)实现解耦与动态装配的核心手段。

通过反射创建实例主要有两种途径:通过 Class.newInstance() 和通过 Constructor.newInstance()。

Class.newInstance() ​

直接通过 Class 对象的 newInstance() 方法创建实例。

java
Class<?> clazz = Class.forName("com.example.User");
// 直接通过 Class 对象实例化(已不推荐使用)
User user = (User) clazz.newInstance();

注意:从 JDK 9 开始,该方法已被标注为 @Deprecated(废弃),官方明确建议使用 Constructor.newInstance() 替代。

Constructor.newInstance() ​

先获取目标类的 Constructor(构造器)对象,再通过构造器对象的 newInstance(...) 方法创建实例。这是现代 Java 中官方推荐的唯一标准方式。

通过 Constructor 实例化支持更丰富的场景,涵盖无参、有参以及私有构造方法的调用。

调用无参构造方法 ​

通过 getDeclaredConstructor() 不传参数,获取默认的无参构造器:

java
Class<User> clazz = User.class;

// 1. 获取无参构造器
Constructor<User> constructor = clazz.getDeclaredConstructor();

// 2. 实例化对象
User user = constructor.newInstance();
调用有参构造方法 ​

根据构造方法的入参类型匹配对应的构造器:

java
Class<User> clazz = User.class;

// 1. 指定入参类型:String.class 和 int.class
Constructor<User> constructor = clazz.getDeclaredConstructor(String.class, int.class);

// 2. 传入具体的实参创建对象
User user = constructor.newInstance("张三", 25);
调用私有构造方法 ​

如果类的构造方法是 private 修饰的(例如单例模式或工具类),可以通过暴力反射(.setAccessible(true)) 破除访问权限限制,否则会报 IllegalAccessException:

java
Class<User> clazz = User.class;

// 1. 获取私有构造器
Constructor<User> constructor = clazz.getDeclaredConstructor(String.class);

// 2. 爆破,压制 Java 语言访问检查(打破 private 约束)
constructor.setAccessible(true);

// 3. 强行创建实例
User user = constructor.newInstance("私有初始化参数");

操作成员属性 ​

对类属性(字段)的操作由 java.lang.reflect.Field 类提供支持,包含获取字段定义、读取字段值和修改字段值三个核心环节。

获取 Field 对象 ​

Class 类提供了两种主要的字段获取途径:getField(String name) 和 getDeclaredField(String name),其作用域差异显著:

java
Class<?> clazz = User.class;

// 1. getField(s):仅获取本类及父类中的 public 成员属性
Field publicField = clazz.getField("nickname");

// 2. getDeclaredField(s):获取本类显式声明的所有成员属性(包含 private/protected),但不包含父类属性
Field privateField = clazz.getDeclaredField("password");

读取与修改属性值 ​

通过 Field 的 get(Object obj) 和 set(Object obj, Object value) 方法操作实例变量:

java
User user = new User("张三");
Class<?> clazz = user.getClass();

// 1. 读取属性
Field nameField = clazz.getDeclaredField("name");
// 若属性为私有,必须设置暴力反射权限
nameField.setAccessible(true);
Object currentValue = nameField.get(user); // 返回 "张三"

// 2. 修改属性
nameField.set(user, "李四"); // 将 user 实例的 name 修改为 "李四"

操作静态与私有属性 ​

针对 static 静态字段和 private 私有字段的操作规则如下:

  • 私有属性(Private):必须显式调用 field.setAccessible(true) 抑制 Java 语言访问权限检查,否则抛出 IllegalAccessException。
  • 静态属性(Static):由于静态属性属于类本身而非具体实例,在调用 get / set 时,目标实例参数直接传 null 即可(例如 field.get(null))。

操作成员方法 ​

对类方法的操作由 java.lang.reflect.Method 类提供支持,通过 invoke() 方法实现函数的动态派发。

获取 Method 对象 ​

获取方法时,必须同时指定方法名称和参数类型列表,以准确区分重载(Overload)方法:

java
Class<?> clazz = User.class;

// 获取 public 方法(包含继承方法):指定方法名和参数类型
Method publicMethod = clazz.getMethod("setName", String.class);

// 获取本类声明的方法(包含 private):无参方法传空或不传
Method privateMethod = clazz.getDeclaredMethod("calculateAge");

执行方法 ​

通过 method.invoke(Object obj, Object... args) 执行目标函数:

java
User user = new User();
Method setNameMethod = User.class.getMethod("setName", String.class);

// 执行 setName("王五")
Object returnVal = setNameMethod.invoke(user, "王五");

特殊场景处理 ​

在调用不同类型的方法时,invoke 的参数传递规则有所不同:

  • 无返回值方法(void):invoke 执行后返回 null。
  • 有返回值方法:invoke 执行后如果有返回值,统一使用 Object 接收。
  • 静态方法(static):invoke(null, args),第一个实例参数传 null。
  • 私有方法(private):执行前必须调用 method.setAccessible(true)。
  • 数组/变长参数方法:当方法入参包含数组时,必须将数组包装为 Object 类型(如 method.invoke(obj, (Object) new String[]{"A", "B"})),防止 JVM 将数组展开拆解为多个独立参数导致参数不匹配。

本章作业 ​

练习 1:通过反射修改私有成员变量 ​

java
// Ex01.java
class PrivateTest {
    private String name = "hellokitty";
    public String getName() {
        return name;
    }
}

// 要求:利用Class类得到私有name属性,修改其值,并调用getName()打印

练习 2:利用反射和 File 创建文件 ​

java
// Ex02.java
// 要求:
// 1. 利用Class类的forName方法得到File类的class对象
// 2. 在控制台打印File类的所有构造器
// 3. 通过newInstance方法创建File对象,并创建E:\mynew.txt文件
// 提示:正常创建文件:File file = new File("d:\\aa.txt"); file.createNewFile();